package com.zed.infrastructure.persistence.mybatis.plus.core;

import cn.hutool.core.util.ReflectUtil;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.MybatisFlexBootstrap;
import com.mybatisflex.core.table.TableInfoFactory;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;

/**
 * 基于Mybatis-Flex的实体访问器 {@html https://mybatis-flex.com/zh/intro/getting-started.html}
 * <p>
 * //TODO事务
 */
public abstract class EntityAccessObject<Entity> extends CustomServiceImpl<BaseMapper<Entity>, Entity> implements ApplicationContextAware {

    private DataSource dataSource;

    private MybatisFlexBootstrap mybatisFlexBootstrap;

    private Class<? extends BaseMapper<Entity>> baseMapperClazz;

    private ApplicationContext applicationContext;

    private BaseMapperWrapper<Entity> mapperWrapper;

    private BaseMapper<Entity> originMapper;

    private Set<Class<?>> ignoreTypes;


    protected EntityAccessObject(Class<? extends BaseMapper<Entity>> baseMapperClazz) {
        this.baseMapperClazz = Objects.requireNonNull(baseMapperClazz, "\"baseMapper\" cannot be null.");
    }

    /**
     * 反射获取忽略类型的集合,该操作是个全局性的操作,谨慎添加,因为{@link TableInfoFactory}类属性使用了static关键字
     *
     * @param clazz {@link Class},需要添加的忽略类型
     */
    protected void ignoreClass(Class<?>... clazz) {
        synchronized (EntityAccessObject.class) {
            if (this.ignoreTypes == null) {
                Field ignoreColumnTypes = ReflectUtil.getField(TableInfoFactory.class, "ignoreColumnTypes");
                Object staticFieldValue = ReflectUtil.getStaticFieldValue(ignoreColumnTypes);
                if (staticFieldValue instanceof Set) {
                    this.ignoreTypes = (Set<Class<?>>) staticFieldValue;
                }
            }

            if (this.ignoreTypes != null) {
                this.ignoreTypes.addAll(Arrays.asList(clazz));
            }
        }

    }

    @PostConstruct
    protected void init() {
        if (this.mybatisFlexBootstrap == null) {
            synchronized (this) {
                if (this.mybatisFlexBootstrap == null) {
                    this.mybatisFlexBootstrap = new CustomMybatisFlexBootstrap();
                    this.ignoreClass(ApplicationContext.class, ApplicationEventPublisher.class);
                }

                this.mybatisFlexBootstrap.setDataSource(this.getDataSource());
                this.mybatisFlexBootstrap.addMapper(this.baseMapperClazz);

                this.mybatisFlexBootstrap.start();

                this.originMapper = this.mybatisFlexBootstrap.getMapper(this.baseMapperClazz);
                this.mapperWrapper = new BaseMapperWrapper<Entity>(this.originMapper);
                this.mapperWrapper.setApplicationContext(this.getApplicationContext());

            }
        }
    }

    @Override
    public BaseMapper<Entity> getMapper() {
        return this.originMapper;
    }

    /**
     * 获取Mapper包装类
     *
     * @return {@link BaseMapperWrapper}
     */
    public BaseMapper<Entity> getMapperWrapper() {
        return this.mapperWrapper;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 获取应用上下文
     *
     * @return {@link ApplicationContext}
     */
    protected ApplicationContext getApplicationContext() {
        return this.applicationContext;
    }

    /**
     * MybatisFlexBootstrap启动器
     *
     * @return {@link MybatisFlexBootstrap}
     */
    protected MybatisFlexBootstrap getMybatisFlexBootstrap() {
        return this.mybatisFlexBootstrap;
    }

    /**
     * 获取数据源
     *
     * @return {@link DataSource}
     */
    public DataSource getDataSource() {
        return this.dataSource;
    }

    @Resource
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }


}
